//-----------------------------------------------------------------------
// <copyright file="User.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.Live.Messenger
{
    using System;
    using System.ComponentModel;
    using Microsoft.Live.Core;

    using Microsoft.Live.Messenger.Messaging;
    using Microsoft.Live.Messenger.Presence;

    /// <summary>
    /// Represents a Windows Live Messenger User.
    /// </summary>
    /// <remarks>
    /// You can create an instance of the User object using the <see cref="AuthenticationCompletedEventArgs.Identity"/> returned by the Sign-in Control. Use the User object to manage all messaging activity (conversations, online presence,contacts, groups, etc.). The User object exists only for the life of the hosting Web page. A single instance can be accessed after a User is signed in. The User object goes away when the User navigates away from the page, refreshes the page, or closes the browser.
    /// </remarks>
    public class User : INotifyPropertyChanged, IDisposable
    {
        private readonly MessengerEntity entity;

        private readonly MessengerIdentity identity;

        private readonly ConversationFactory conversationFactory;
        private readonly EndpointFactory endpointFactory;
        private readonly IMAddressFactory addressFactory;
        private readonly ContactFactory contactFactory;
        private readonly MessageFactory messageFactory;
        private readonly GroupFactory groupFactory;

        private readonly IMAddress address;
        private readonly Endpoint localEndpoint;

        private Settings settings;
        private ConversationCollection conversations;
        private ContactCollection contacts;
        private ContactCollection allowedContacts;
        private ContactCollection blockedContacts;
        private PendingContactCollection pendingContacts;
        private GroupCollection groups;
        private Mailbox mailbox;

        /// <summary>
        /// Initializes a new instance of the <see cref="User"/> class.
        /// </summary>
        /// <param name="identity">The identity (Live ID) of the User.</param>
        public User(Identity identity)
        {
            if (identity == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("identity");
            }

            var msgrIdentity = identity as MessengerIdentity;
            if (msgrIdentity == null)
            {
                throw new ArgumentException("Unsupported type", "identity");
            }
            this.identity = msgrIdentity;

            this.entity = MessengerEntity.CreateInstance(typeof(User), this.identity.Entity);
            this.entity.AttachEvent("AddContactCompleted", this.OnAddContactCompleted);
            this.entity.AttachEvent("PropertyChanged", this.OnPropertyChanged);
            this.entity.AttachEvent("SignInCompleted", this.OnSignInCompleted);
            this.entity.AttachEvent("SignOutCompleted", this.OnSignOutCompleted);
            this.entity.AttachEvent("SignOutEndpointCompleted", this.OnSignOutEndpointCompleted);
            this.entity.AttachEvent("SignedOutRemotely", this.OnSignedOutRemotely);

            this.conversationFactory = new ConversationFactory(this);
            this.endpointFactory = new EndpointFactory(this);
            this.addressFactory = new IMAddressFactory(this);
            this.contactFactory = new ContactFactory(this);
            this.messageFactory = new MessageFactory(this);
            this.groupFactory = new GroupFactory(this);

            this.address = this.addressFactory.FindOrCreate(this.entity.GetProperty("Address"));
            this.localEndpoint = this.address.Endpoints[0];
        }

        internal MessengerEntity Entity
        {
            get { return this.entity; }
        }

        internal ConversationFactory ConversationFactory
        {
            get { return this.conversationFactory; }
        }

        internal EndpointFactory EndpointFactory
        {
            get { return this.endpointFactory; }
        }

        internal IMAddressFactory IMAddressFactory
        {
            get { return this.addressFactory; }
        }

        internal ContactFactory ContactFactory
        {
            get { return this.contactFactory; }
        }

        internal MessageFactory MessagesFactory
        {
            get { return this.messageFactory; }
        }

        internal GroupFactory GroupFactory
        {
            get { return this.groupFactory; }
        }

        #region Properties

        /// <summary>
        /// Gets the <see cref="Identity"/> (Live ID) for the User. 
        /// </summary>
        public Identity Identity
        {
            get { return this.identity; }
        }

        /// <summary>
        /// Gets the <see cref="IMAddress"/> for the User.
        /// </summary>
        public IMAddress Address
        {
            get { return this.address; }
        }

        /// <summary>
        /// Gets the <see cref="IMAddressPresence"/> for the User.
        /// </summary>
        public IMAddressPresence Presence
        {
            get { return this.address.Presence; }
        }

        /// <summary>
        /// Gets the <see cref="EndpointCollection"/> for the User.
        /// </summary>
        public EndpointCollection Endpoints
        {
            get { return this.address.Endpoints; }
        }

        /// <summary>
        /// Gets the local <see cref="Endpoint"/> for the User.
        /// </summary>
        public Endpoint LocalEndpoint
        {
            get { return this.localEndpoint; }
        }

        /// <summary>
        /// Gets the <see cref="ConversationCollection"/> for the User.
        /// </summary>
        public ConversationCollection Conversations
        {
            get
            {
                if (this.conversations == null)
                {
                    this.conversations = new ConversationCollection(this.entity.GetProperty("Conversations"), this);
                }
                return this.conversations;
            }
        }

        /// <summary>
        /// Gets the <see cref="ContactCollection"/> containing all of the User's contacts.
        /// </summary>
        public ContactCollection Contacts
        {
            get
            {
                if (this.contacts == null)
                {
                    this.contacts = new ContactCollection(this.entity.GetProperty("Contacts"), this);
                }
                return this.contacts;
            }
        }

        /// <summary>
        /// Gets the <see cref="ContactCollection"/> containing all of the User's allowed contacts.
        /// </summary>
        public ContactCollection AllowedContacts
        {
            get
            {
                if (this.allowedContacts == null)
                {
                    this.allowedContacts = new ContactCollection(this.entity.GetProperty("AllowedContacts"), this);
                }
                return this.allowedContacts;
            }
        }

        /// <summary>
        /// Gets the <see cref="ContactCollection"/> containing all of the User's blocked contacts.
        /// </summary>
        public ContactCollection BlockedContacts
        {
            get
            {
                if (this.blockedContacts == null)
                {
                    this.blockedContacts = new ContactCollection(this.entity.GetProperty("BlockedContacts"), this);
                }
                return this.blockedContacts;
            }
        }

        /// <summary>
        /// Gets the <see cref="ContactCollection"/> containing all of the User's pending contacts.
        /// </summary>
        public PendingContactCollection PendingContacts
        {
            get
            {
                if (this.pendingContacts == null)
                {
                    this.pendingContacts = new PendingContactCollection(this.entity.GetProperty("PendingContacts"), this);
                }
                return this.pendingContacts;
            }
        }

        /// <summary>
        /// Gets the <see cref="GroupCollection"/> containing all of the User's groups.
        /// </summary>
        public GroupCollection Groups
        {
            get
            {
                if (this.groups == null)
                {
                    this.groups = new GroupCollection(this.entity.GetProperty("Groups"), this);
                }
                return this.groups;
            }
        }

        /// <summary>
        /// Gets the <see cref="Settings"/> containing the User's privacy options.
        /// </summary>
        public Settings Settings
        {
            get
            {
                if (this.settings == null)
                {
                    this.settings = new Settings(this.entity.GetProperty("Settings"));
                }
                return this.settings;
            }
        }

        /// <summary>
        /// Gets the User's <see cref="Mailbox"/>.
        /// </summary>
        public Mailbox Mailbox
        {
            get { return this.mailbox; }
        }

        /// <summary>
        /// The message factory that allows for custom messages to be sent and received.
        /// </summary>
        public ApplicationMessageFactory MessageFactory
        {
            get { return null; }
            set { throw new NotImplementedException(); }
        }

        /// <summary>
        /// The presence factory that allows custom presence properties to be used.
        /// </summary>
        public PresenceExtensionFactory PresenceFactory
        {
            get { return null; }
            set { throw new NotImplementedException();  }
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when <see cref="AddContact"/> has completed.
        /// </summary>
        public event AddContactCompletedEventHandler AddContactCompleted;

        /// <summary>
        /// Occurs when the User has been signed out remotely.
        /// </summary>
        public event SignedOutRemotelyEventHandler SignedOutRemotely;

        /// <summary>
        /// Occurs when <see cref="SignIn"/> has completed.
        /// </summary>
        public event SignInCompletedEventHandler SignInCompleted;

        /// <summary>
        /// Occurs when <see cref="SignOut"/> has completed.
        /// </summary>
        public event SignOutCompletedEventHandler SignOutCompleted;

        /// <summary>
        /// Occurs when <see cref="SignOutEndpoint"/> has completed.
        /// </summary>
        public event SignOutCompletedEventHandler SignOutEndpointCompleted;

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when the value of a property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #endregion

        public void SignIn()
        {
            this.SignIn(null);
        }

        /// <summary>
        /// Signs the User into the Messenger service. 
        /// </summary>
        /// <param name="userState">
        /// A User-defined object that is passed to the method invoked when the asynchronous operation completes.
        /// </param>
        /// <exception cref="Exception">The User is in the process of signing in or is already signed in.</exception>
        public void SignIn(
            object userState)
        {
            this.mailbox = this.OnCreateMailbox();

            this.entity.Invoke("SignIn", userState);

            this.mailbox.Initialize();
        }

        public void SignOut(SignOutLocations locations)
        {
            this.SignOut(locations, null);
        }

        /// <summary>
        /// Signs the provided locations out from the service.
        /// </summary>
        /// <param name="locations">
        /// The locations that should be signed out from the service.
        /// </param>
        /// <param name="userState">
        /// A User-defined object that is passed to the method invoked when the asynchronous operation completes.
        /// </param>
        /// <exception cref="Exception">The User is not signed in.</exception>
        public void SignOut(
            SignOutLocations locations,
            object userState)
        {
            this.entity.Invoke("SignOut", locations, userState);
        }

        public void SignOutEndpoint(Endpoint endpoint)
        {
            this.SignOutEndpoint(endpoint, null);
        }

        /// <summary>
        /// Signs the provided endpoint out from the service.
        /// </summary>
        /// <param name="endpoint">
        /// The endpoint to be signed out.
        /// </param>
        /// <param name="userState">
        /// A User-defined object that is passed to the method invoked when the asynchronous operation completes.
        /// </param>
        public void SignOutEndpoint(
            Endpoint endpoint,
            object userState)
        {
            if (endpoint == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("endpoint");
            }

            if (!this.address.Endpoints.Contains(endpoint))
            {
                throw ExceptionHelper.CreateArgumentOutOfRangeException("endpoint");
            }

            this.entity.Invoke("SignOutEndpoint", endpoint.Entity, userState);
        }

        public void AddContact(
            string address)
        {
            this.AddContact(address, null, null);
        }

        public void AddContact(
            string address,
            string inviteMessage)
        {
            this.AddContact(address, inviteMessage, null);
        }

        /// <summary>
        /// Adds a contact to the User's buddy list.
        /// </summary>
        /// <param name="address">The address of the contact to add.</param>
        /// <param name="inviteMessage">The invitation message to display to the invited contact.</param>
        /// <param name="userState">
        /// A User-defined object that is passed to the method invoked when the asynchronous operation completes.
        /// </param>
        /// <exception cref="Exception">The User is not signed in.</exception>
        public void AddContact(
            string address,
            string inviteMessage,
            object userState)
        {
            if (String.IsNullOrEmpty(address))
            {
                throw ExceptionHelper.CreateArgumentNullException("address");
            }

            if (inviteMessage == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("inviteMessage");
            }

            this.entity.Invoke("AddContact", address, inviteMessage, userState);
        }

        private void OnAddContactCompleted(object sender, MessengerEventArgs e)
        {
            this.OnAddContactCompleted(
                new AddContactCompletedEventArgs(
                    e.Entity.GetEnumProperty<AddContactResultCode>("ResultCode"),
                    this.contactFactory.FindOrCreate(e.Entity.GetProperty("Contact")),
                    e.Entity.GetProperty("UserState")));
        }

        /// <summary>
        /// Raises the <see cref="AddContactCompleted"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="AddContactCompletedEventArgs"/> object containing event data.
        /// </param>
        protected internal virtual void OnAddContactCompleted(AddContactCompletedEventArgs e)
        {
            if (this.AddContactCompleted != null)
            {
                this.AddContactCompleted(this, e);
            }
        }

        private void OnSignInCompleted(object sender, MessengerEventArgs e)
        {
            this.OnSignInCompleted(
                new SignInCompletedEventArgs(
                    e.Entity.GetEnumProperty<SignInResultCode>("ResultCode"),
                    e.Entity.GetProperty("UserState")));
        }

        /// <summary>
        /// Raises the <see cref="SignInCompleted"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="SignInCompletedEventArgs"/> object containing event data.
        /// </param>
        protected internal virtual void OnSignInCompleted(SignInCompletedEventArgs e)
        {
            if (this.SignInCompleted != null)
            {
                this.SignInCompleted(this, e);
            }
        }

        private void OnSignOutCompleted(object sender, MessengerEventArgs e)
        {
            this.OnSignOutCompleted(
                new SignOutCompletedEventArgs(
                    e.Entity.GetEnumProperty<SignOutResultCode>("ResultCode"),
                    e.Entity.GetProperty("UserState")));
        }

        /// <summary>
        /// Raises the <see cref="SignOutCompleted"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="SignOutCompletedEventArgs"/> object containing event data.
        /// </param>
        protected internal virtual void OnSignOutCompleted(SignOutCompletedEventArgs e)
        {
            if (this.SignOutCompleted != null)
            {
                this.SignOutCompleted(this, e);
            }
        }

        private void OnSignOutEndpointCompleted(object sender, MessengerEventArgs e)
        {
            this.OnSignOutEndpointCompleted(
                new SignOutCompletedEventArgs(
                    e.Entity.GetEnumProperty<SignOutResultCode>("ResultCode"),
                    e.Entity.GetProperty("UserState")));
        }

        /// <summary>
        /// Raises the <see cref="SignOutEndpointCompleted"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="SignOutCompletedEventArgs"/> object containing event data.
        /// </param>
        protected internal virtual void OnSignOutEndpointCompleted(SignOutCompletedEventArgs e)
        {
            if (this.SignOutEndpointCompleted != null)
            {
                this.SignOutEndpointCompleted(this, e);
            }
        }

        private void OnSignedOutRemotely(object sender, MessengerEventArgs e)
        {
            this.OnSignedOutRemotely(
                new SignedOutRemotelyEventArgs());
        }

        /// <summary>
        /// Raises the <see cref="SignedOutRemotely"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="SignedOutRemotelyEventArgs"/> object containing event data.
        /// </param>
        protected internal virtual void OnSignedOutRemotely(SignedOutRemotelyEventArgs e)
        {
            if (this.SignedOutRemotely != null)
            {
                this.SignedOutRemotely(this, e);
            }
        }

        /// <summary>
        /// Occurs when the User's mailbox is created.
        /// </summary>
        /// <returns>The mailbox.</returns>
        protected virtual Mailbox OnCreateMailbox()
        {
            return new Mailbox(this);
        }

        internal Conversation CreateConversation(IMAddress address)
        {
            return this.OnCreateConversation(address);
        }

        /// <summary>
        /// Occurs when a conversation is created.
        /// </summary>
        /// <param name="address">The instant messaging address associated with the conversation.</param>
        /// <returns>The conversation.</returns>
        protected virtual Conversation OnCreateConversation(
            IMAddress address)
        {
            if (address == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("address");
            }

            return new Conversation(this, address);
        }

        private void OnPropertyChanged(object sender, MessengerEventArgs e)
        {
            this.OnPropertyChanged(
                new PropertyChangedEventArgs(
                    e.Entity.GetProperty<string>("PropertyName")));
        }

        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, e);
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Disposes the service.
        /// </summary>
        public void Dispose()
        {
            this.entity.Invoke("Dispose");
        }

        #endregion
    }
}
